package Code.Objects.Characters.Player;

import java.util.Random;

import org.jrabbit.base.graphics.transforms.Vector2d;
import org.jrabbit.base.input.KeyboardHandler;
import org.jrabbit.base.input.MouseHandler;
import org.jrabbit.base.managers.ResourceManager;
import org.jrabbit.base.managers.window.WindowManager;
import org.jrabbit.standard.game.managers.GameManager;
import org.jrabbit.standard.game.objects.text.TextSprite;
import org.lwjgl.input.Keyboard;

import Code.Basic.Entity.GenericCharacter;
import Code.Objects.Characters.Player.GUI.*;
import Code.Objects.Characters.Player.Misc.*;
import Code.Objects.Characters.Player.Movement.*;
import Code.Objects.Characters.Player.Skins.PlayerFeetSkin;
import Code.Objects.Characters.Player.Weapons.PlayerWeapon;
import Code.Objects.Characters.Player.Weapons.Ranged.AmmoBased.RapidFire.MachineGun.PlayerMachineGun;
import Code.Objects.Characters.Player.Weapons.Ranged.AmmoBased.SingleFire.Shotgun.PlayerShotgun;
import Code.Objects.Characters.Player.Weapons.Ranged.Energy.Laser.TestLaserRifle;
import Code.Objects.Characters.Enemies.EnemyGenerator;

public class Player extends GenericCharacter
{
	// ************************************* Input/movement controls *************
	private float moveForce;
	private PlayerFeetOrientationInfo feetInfo;
	private PlayerFeetOrienter feetOrienter;
	private boolean[][] footstepExpected;
	private float[] footstepVolume;
	
	// ************************************* Camera variables *********************
	private boolean useWeaponCameraMode;
	private int playerPreferredCameraMode;
	private int cameraMode;
	private double cameraMoveSpeed;
	private static final int CAMERA_LOCKED = 0;
	private static final int CAMERA_SWING = 1;
	private static final int CAMERA_AIM = 2;
	
	// ************************************* Weapon controls **********************
	private PlayerWeapon[] weapons;
	private int weaponChoice;
	private int lastWeapon;
	private static final int MAX_WEAPONS_CARRIED = 3;
	
	//************************************** GUI **********************************
	private HealthGUI healthGUI;
	private Radar radar; 
	private WeaponSelector weaponChoices;
	private ObjectiveFinder pointer;
	private TextSprite scoreText;
	private boolean showGUI;
	
	//**************************** Enemy Generator ********************************
	private EnemyGenerator eG;
	
	public Player()
	{
		super(new PlayerFeetSkin());
		
		defineHealth(100);
		faction = 1;
		factionRatings = new int[] {0, 1, 2};
		
		setUpMovementData();
		setUpWeaponData();
		setUpCameraData();
		setUpGUIData();
		//eG = new EnemyGenerator(this, 10, 0.5, 1500);
		eG = new EnemyGenerator(this);
		
		
	}
	
	// ************************************* Movement methods ***********
	private void setUpMovementData()
	{
		moveForce = 500;
		getBody().setDamping(0.5f);
		setToCircle(60);
		setMass(5);
		feetOrienter = new PlayerFeetOrienter();
		feetInfo = new PlayerFeetOrientationInfo();
		
		footstepExpected = new boolean[][]
		{
				{false},
				{false, false, false, false, false, false, true,
					false, false, false, false, false, false, true},
				{false, false, false, false, false, false, true,
					false, false, false, false, false, false, true},
				{false, false, false, true, false, false, true, false},
				{false, false, false, false, true, false, false, false, false, true},
				{false, false, false, false, true, false, false, false, false, true}
		};
		
		footstepVolume = new float[] {1f, 1f, 1f, 0.5f, 0.5f, 0.5f};
	}

	private int getInputDirection()
	{
		boolean up = KeyboardHandler.isKeyDown(Keyboard.KEY_W) && !KeyboardHandler.isKeyDown(Keyboard.KEY_S);
		boolean down = KeyboardHandler.isKeyDown(Keyboard.KEY_S) && !KeyboardHandler.isKeyDown(Keyboard.KEY_W);
		boolean left = KeyboardHandler.isKeyDown(Keyboard.KEY_A) && !KeyboardHandler.isKeyDown(Keyboard.KEY_D);
		boolean right = KeyboardHandler.isKeyDown(Keyboard.KEY_D) && !KeyboardHandler.isKeyDown(Keyboard.KEY_A);
		
		int result = -1;
		
		if(up)
		{
			if(left)
			{
				result = 5;
			}
			else if(right)
			{
				result = 7;
			}
			else
			{
				result = 6;
			}
		}
		else if(down)
		{
			if(left)
			{
				result = 3;
			}
			else if(right)
			{
				result = 1;
			}
			else
			{
				result = 2;
			}
		}
		else
		{
			if(left)
			{
				result = 4;
			}
			else if(right)
			{
				result = 0;
			}
		}
		
		return result;
	}

	private double getRotationAngle()
	{
		return location.angleTowards(MouseHandler.location(false));
	}
	
	public void move(int direction)
	{
		switch(direction)
		{
			case 0:
				moveForward(moveForce);
					break;
			case 1:
				moveForward(moveForce * 0.7f);
				moveSideways(moveForce * 0.7f);
					break;
			case 2:
				moveSideways(moveForce * 0.75f);
					break;
			case 3:
				moveForward(moveForce * -0.5f);
				moveSideways(moveForce * 0.5f);
					break;
			case 4:
				moveForward(moveForce * -0.6f);
					break;
			case 5:
				moveForward(moveForce * -0.5f);
				moveSideways(moveForce * -0.5f);
					break;
			case 6:
				moveSideways(moveForce * -0.75f);
					break;
			case 7:
				moveForward(moveForce * 0.7f);
				moveSideways(moveForce * -0.7f);
					break;
		}
	}
	
	private void handleMovement()
	{
		int direction = getInputDirection();
		if(direction == -1)
		{
			animation.setCycle(0);
			scalar.setFlipVertically(false);
			rotation.set(40 + Math.round(getWeapon().rotation().angle() / 45f) * 45);
		}
		else
		{
			feetInfo = feetOrienter.getFeetResult(getRotationAngle(), direction);
			setRotation(feetInfo.getRotation());
			scalar.setFlipVertically(feetInfo.isFlipped());
			animation.setCycle(feetInfo.getCycle());
			move(feetInfo.getMovementDirection());
		}
	}
	
	
	
	
	
	// ************************************* Weapon methods ***************
	private void setUpWeaponData()
	{
		weapons = new PlayerWeapon[MAX_WEAPONS_CARRIED];
		weapons[0] = new PlayerShotgun(this);
		weapons[1] = new PlayerMachineGun(this);
		weapons[2] = new TestLaserRifle(this);
		weaponChoice = 1;
		lastWeapon = 0;
	}
	
	public PlayerWeapon getWeapon()
	{
		return weapons[weaponChoice];
	}
	
	public void selectWeapon(int choice)
	{
		lastWeapon = weaponChoice;
		weaponChoice = choice;
		getWeapon().adjust();
		updateCameraMode();
	}
	
	public void nextWeapon()
	{
		lastWeapon = weaponChoice;
		weaponChoice++;
		if(weaponChoice >= MAX_WEAPONS_CARRIED)
		{
			weaponChoice = 0;
		}
		getWeapon().adjust();
		updateCameraMode();
	}
	
	public void previousWeapon()
	{
		lastWeapon = weaponChoice;
		weaponChoice--;
		if(weaponChoice < 0)
		{
			weaponChoice = MAX_WEAPONS_CARRIED - 1;
		}
		getWeapon().adjust();
		updateCameraMode();
	}
	
	public void lastUsedWeapon()
	{
		int wc = weaponChoice;
		weaponChoice = lastWeapon;
		lastWeapon = wc;
		getWeapon().adjust();
		updateCameraMode();
	}
	
	private void handleWeaponSelection()
	{
		if(KeyboardHandler.wasKeyPressed(Keyboard.KEY_TAB))
		{
			lastUsedWeapon();
		}
		if(KeyboardHandler.wasKeyPressed(Keyboard.KEY_Q))
		{
			previousWeapon();
		}
		if(KeyboardHandler.wasKeyPressed(Keyboard.KEY_E))
		{
			nextWeapon();
		}
		if(KeyboardHandler.wasKeyPressed(Keyboard.KEY_1))
		{
			selectWeapon(0);
		}
		if(KeyboardHandler.wasKeyPressed(Keyboard.KEY_2))
		{
			selectWeapon(1);
		}
		if(KeyboardHandler.wasKeyPressed(Keyboard.KEY_3))
		{
			selectWeapon(2);
		}
		if(KeyboardHandler.wasKeyPressed(Keyboard.KEY_4))
		{
			//selectWeapon(3);
		}
	}
	
	public void assignWeapon(PlayerWeapon weapon, int slot)
	{
		weapons[slot] = weapon;
		getWeapon().adjust();
	}
	
	
	
	
	
	// ************************************* Camera methods ***************
	private void setUpCameraData()
	{
		playerPreferredCameraMode = CAMERA_SWING;
		useWeaponCameraMode = true;
		cameraMoveSpeed = 0.225;
		updateCameraMode();
	}
	
	public void setCameraMode(int mode)
	{
		playerPreferredCameraMode = mode;
		updateCameraMode();
	}
	
	private void updateCameraMode()
	{
		if(!useWeaponCameraMode)
		{
			cameraMode = playerPreferredCameraMode;
		}
		else
		{
			cameraMode = getWeaponPreferredCameraMode();
		}
	}
	
	private int getWeaponPreferredCameraMode()
	{
		return getWeapon().getCameraMode();
	}
	
	public void setUseWeaponCameraMode(boolean b)
	{
		useWeaponCameraMode = b;
		updateCameraMode();
	}

	private void handleCamera(int delta)
	{
		Vector2d focusPoint = GameManager.camera().location().copy();
		
		if(cameraMode == CAMERA_LOCKED)
		{
			focusPoint = location();
		}
		else if(cameraMode == CAMERA_SWING)
		{
			Vector2d diff = location.difference(focusPoint);
			diff.multiply(3);
			Vector2d mouseLoc = MouseHandler.location(true).copy();
			mouseLoc.add(-WindowManager.getDisplayWidth() / 2, -WindowManager.getDisplayHeight() / 2);
			diff.add(mouseLoc);
			diff.divide(4);
			double distanceVal = diff.magnitude() - 100;
			
			if(distanceVal > 0)
			{
				focusPoint.addPolar(distanceVal * distanceVal * delta * 0.0001, Math.toRadians(diff.direction()));
			}
		}
		else if(cameraMode == CAMERA_AIM)
		{
			focusPoint.set(location());
			focusPoint.multiply(2);
			focusPoint.add(MouseHandler.location(false));
			focusPoint.divide(3);
		}
		
		moveCameraToFocusPoint(focusPoint, delta);
	}
	
	private void moveCameraToFocusPoint(Vector2d target, int delta)
	{
		GameManager.camera().location().moveTowards(target, cameraMoveSpeed * delta);
	}
	
	
	
	
	// ************************************* GUI methods *******************
	public void setUpGUIData()
	{
		healthGUI = new HealthGUI(this);
		radar = new Radar(this);
		weaponChoices = new WeaponSelector();
		pointer = new ObjectiveFinder();
		scoreText = new TextSprite("Score: "+score);
		scoreText.useScreenCoords(true);
		scoreText.location().set(WindowManager.getDisplayWidth() - 300, 24);
		showGUI = true;
	}
	
	public void renderObjectives()
	{
		pointer.render(location());
	}
	
	public void renderGUI()
	{
		if(showGUI && health > 0)
		{
			getWeapon().renderGUI();
			healthGUI.render(this);
			radar.render();
			scoreText.render();
			weaponChoices.render(weapons, weaponChoice);
		}
	}
	
	public void handleGUI(int delta)
	{
		healthGUI.update(delta);
		radar.update(delta);
		pointer.update(delta);
		scoreText.setText("Score: "+score);
	}
	
	public void showGUI(boolean b)
	{
		showGUI = b;
	}
	
	public boolean isShowingGUI()
	{
		return showGUI;
	}
	
	public ObjectiveFinder getObjectiveFinder()
	{
		return pointer;
	}
	
	
	
	
	// **************************** Enemy Generator Methods ****************
	public void setEnemyGeneratorEnable()
	{
		eG.Enable();
	}
	
	public void setEnemyGeneratorDisable()
	{
		eG.Disable();
	}
	
	public void setEnemyGeneratorParameters(int mE, double d)
	{
		eG.setParameters(mE, d);
	}
	
	
	// ************************************* Overridden methods ************
	public void onFrame(int cycle, int frame)
	{
		if(footstepExpected[cycle][frame])
		{
			PlayerSounds.playFootstep(footstepVolume[cycle]);
		}
	}
	
	public void render()
	{
		super.render();
		getWeapon().render();
	}
	
	public void update(int delta)
	{
		super.update(delta);
		handleMovement();
		handleCamera(delta);
		handleWeaponSelection();
		handleGUI(delta);
		getWeapon().update(delta);
		eG.generate(delta);
		
		
		if(KeyboardHandler.wasKeyPressed(Keyboard.KEY_F1))
		{
			showGUI = !showGUI;
		}
		if(KeyboardHandler.wasKeyPressed(Keyboard.KEY_F2))
		{
			pointer.addTarget(location);
		}
		if(KeyboardHandler.wasKeyPressed(Keyboard.KEY_F3))
		{
			Random r = ResourceManager.random();
			int numPoints = r.nextInt(10) + 5;
			Vector2d points[] = new Vector2d[numPoints];
			double startX = location.x() + (r.nextDouble() * 300) - 150;
			double startY = location.y() + (r.nextDouble() * 300) - 150;
			double directionX = (r.nextDouble() * 2000) - 1000;
			double directionY = (r.nextDouble() * 2000) - 1000;
			
			points[0] = location.copy();
			for(int i = 1; i < numPoints; i++)
			{
				startX += (r.nextDouble() * 600) - 300 + directionX;
				startY += (r.nextDouble() * 600) - 300 + directionY;
				points[i] = new Vector2d(startX, startY);
				directionX += (r.nextDouble() - 0.5) * 350;
				directionY += (r.nextDouble() - 0.5) * 350;
			}
			pointer.addTarget(points);
		}
		if(KeyboardHandler.wasKeyPressed(Keyboard.KEY_F4))
		{
			pointer.clearTargets();
		}
	}
}
